#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# DeviceSystem.py
# Copyright (c) 2010 - 2011 Matthew Reid

import os
import socket
import threading
#import jsonpickle

from Bases import OSCBaseObject, BaseThread, ChildGroup, Category, Serialization

from Universe import Universe, InUniverse

from Devices import Device
from Attributes import Attribute

class DeviceSystem(OSCBaseObject):
    _saved_class_name = 'DeviceSystem'
    _saved_child_objects = ['universes', 'input_universes', 'Categories', 'GlobalAttributes', 'DefinedDevices', 'PatchedDevices']
    _saved_child_classes = [Universe, Category, Attribute.GlobalAttribute, 
                            Device.DefinedDevice, Device.PatchedDevice]
    _ChildGroups = {'universes':dict(child_class=Universe, 
                                     send_child_updates_to_osc=True), 
                    'input_universes':dict(child_class=InUniverse, 
                                           send_child_updates_to_osc=True), 
                    'GlobalAttributes':dict(child_class=Attribute.GlobalAttribute, 
                                            send_child_updates_to_osc=True), 
                    'DefinedDevices':dict(child_class=Device.DefinedDevice, 
                                          send_child_updates_to_osc=True), 
                    'PatchedDevices':dict(child_class=Device.PatchedDevice, 
                                          send_child_updates_to_osc=True, 
                                          osc_address='Devices')}
    def __init__(self, **kwargs):
        self.MainController = kwargs.get('MainController')
        self.comm = self.MainController.comm
        #self.osc_parent_node = self.comm.osc_io.root_node
        #self.osc_address = 'Devices'
        self.Categories = {}
        #self.root_category = Category(name='root', id='root')
        self.root_category = self.GLOBAL_CONFIG['ROOT_CATEGORY']
        for key in ['device', 'attribute', 'effects']:
            category = self.root_category.add_subcategory(name=key, id=key)
            self.Categories.update({key:category.subcategories})
        super(DeviceSystem, self).__init__(**kwargs)
        archive = self.MainController.Archive
        for key in DeviceSystem._saved_child_objects:
            archive.add_member(name=key, 
                               path='DeviceSystem', 
                               filename='%s.js' % (key), 
                               serialize_obj={'DeviceSystem':self}, 
                               serialize_kwargs={'DeviceSystem':{'saved_child_objects':[key]}}, 
                               deserialize_kwargs={'DeviceSystem':{'saved_child_objects':[key]}})
        archive.add_member(name='Patch List', 
                           path='DeviceSystem', 
                           filename='PatchList.csv', 
                           serialize_method='csv', 
                           serialize_obj=self.get_patch_list, 
                           serialize_kwargs={'add_index':False}, 
                           csv_columns=['Index', 'name', 'universe', 'start_chan', 'end_chan', 'definition'])
        self.osc_dump_threads = set()
        self.dump_request_thread = None
        self.osc_child_nodes = {}
        
        self.add_osc_handler(callbacks={'request-dump':self.on_osc_request_dump})
                                        #'dump-response':self.on_osc_dump_response, 
                                        #'child-added':self.on_osc_child_added})
        self.osc_universe_node = self.comm.osc_io.root_node.add_child(name='OSCtoOLA')
        #self.osc_universe_node.addCallback('OSCtoOLA/universes-info', self.on_osc_universes_info)        
        
        #self.DeviceTypes = DeviceTypes().get_types()
        self.universes_by_id = {}
        self.universes.bind(child_added=self._on_universes_child_added, 
                            child_removed=self._on_universes_child_removed)
        self.PatchedDevices.bind(child_added=self._on_PatchedDevices_child_added)
        self.DevicesByNum = {}
        self.patch_by_chan = {}
        self.patch_by_attrib = {}
        
        self.category_type_map = {'device':[self.DefinedDevices, self.PatchedDevices], 
                                  'attribute':[self.GlobalAttributes, self.patch_by_attrib]}
    
        self.register_signal('child_added', 'new_category', 'new_universe', 'new_definition', 
                             'new_device', 'new_attribute', 'del_attribute', 'new_input_universe', 
                             'del_device', 'patch_change')
        
        #self.connect('new_universe', self.comm.on_new_universe)
        self.comm.connect('state_changed', self.on_comm_state)
        #self.connect('new_category', self._on_new_category)
        #self.add_universe(name='A', ola_universe_id=1)
        self.osc_node.send_interrupt_recursive = True
        self.osc_node.children['request-dump'].send_interrupt_recursive = False
            
    def get_patch_list(self):
        l = []
        archive = self.MainController.Archive.members.get('DeviceSystem/PatchList.csv')
        if not archive:
            return l
        keys = archive.csv_columns
        for device in self.PatchedDevices.indexed_items.itervalues():
            d = {}
            for key in keys:
                if key == 'universe':
                    univ = self.universes[device.universe_id]
                    d[key] = univ.Index
                elif key == 'definition':
                    d[key] = device.definition.name
                else:
                    d[key] = getattr(device, key)
            l.append(d)
        return l
            
    def on_osc_request_dump(self, **kwargs):
        if self.osc_node.oscMaster:
            values = kwargs.get('values')
            client = kwargs.get('client')
            i = 0
            if len(values):
                for key in values:
                    val = getattr(self, key)
                    if key == 'Categories':
                        s = self.to_json(saved_child_objects=[key], indent=None)
                        self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
                        i += 1
                    else:
                        for ckey, cval in val.iteritems():
                            s = cval.to_json(indent=None)
                            d = Serialization.from_json(s)
                            s = Serialization.to_json({'saved_children':{key:{d['attrs']['id']:d}}})
                            self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
                            i += 1
            else:
                s = self.to_json(indent=None)
                self.osc_node.send_message(address='dump-response', value=[i, s], client=client)
            self.osc_node.send_message(address='dump-response', value='dump-complete', client=client)
        
    def load_osc_dump(self, dump):
        self.osc_node.send_interrupt_enabled = True
        keys = sorted(dump.keys())
        for key in keys:
            self.from_json(string=dump[key])
        self.osc_node.send_interrupt_enabled = False
        for device in self.PatchedDevices.itervalues():
            device.request_values_from_osc(to_master=True)
        
    def request_osc_updates(self, **kwargs):
        if self.dump_request_thread is not None:
            self.dump_request_thread.stop(blocking=True)
            self.dump_request_thread = None
        self.dump_request_thread = OSCDumpRequest(DeviceSystem=self)
        self.dump_request_thread.bind(dump_complete=self.on_dump_request_thread_dump_complete)
        self.dump_request_thread.start()
        return self.dump_request_thread
        
    def on_dump_request_thread_dump_complete(self, **kwargs):
        self.dump_request_thread = None
        
    def on_comm_state(self, **kwargs):
        if kwargs.get('state'):
            self.osc_universe_node.send_message(root_address='OSCtoOLA-'+socket.gethostname(), 
                                                address='request-universes')
            
    def on_osc_universes_info(self, **kwargs):
        values = kwargs.get('values')
        d = Serialization.from_json(values[0])
        self.LOG.info('univ info from osc:', d)
    
#    def _on_new_category(self, **kwargs):
#        category = kwargs.get('category')
#        category.connect('obj_update_needed', self._on_category_obj_request)
#        category._check_member_obj()
#    
#    def _on_category_obj_request(self, **kwargs):
#        id = kwargs.get('category_id')
#        obj_id = kwargs.get('obj_id')
#        callback = kwargs.get('callback')
#        for cTypeKey, cTypeVal in self.Categories.iteritems():
#            for catKey, catVal in cTypeVal.iteritems():
#                if catVal.id == id:
#                    category = catVal
#                    category_type = cTypeKey
#        obj = None
#        for obj_dict in self.category_type_map[category_type]:
#            if obj_id in obj_dict:
#                obj = obj_dict[obj_id]
#        callback(obj=obj, id=obj_id)
#    
#    def get_stuff(self):
#        return 'stuff'
    
    def add_universe(self, **kwargs):
        '''
        :Parameters:
            'id' : int, id for the universe, does not auto-assign UUID
            'type': type of universe ('input', 'output'). default is 'output'
            'name' : str
            'from_xml' : xml string which, if specified, the xml will be loaded
        '''
        #id = kwargs.get('id', self.find_next_universe_id())
        name = kwargs.get('name')
        utype = kwargs.get('type', 'output')
        new_kwargs = kwargs.copy()
        if 'type' in new_kwargs:
            del new_kwargs['type']
        keys = ['Artnet_Subnet', 'Artnet_Universe']
        if utype == 'output':
            childgroup = self.universes
            sig = 'new_universe'
        else:
            childgroup = self.input_universes
            sig = 'new_input_universe'
        i = childgroup.find_max_index()
        new_kwargs.setdefault('name', 'Universe %s' % (i + 1))
        if i == 0:
            new_kwargs['Index'] = 1
        
        if len(childgroup):
            last_u = childgroup.indexed_items[childgroup.find_max_index()]
            
            d = dict(zip(keys, [getattr(last_u, key) for key in keys]))
            if d[keys[1]] == 15:
                d[keys[1]] = 0
                d[keys[0]] += 1
            else:
                d[keys[1]] += 1
        else:
            d = dict(zip(keys, [0, 0]))
        new_kwargs.update(d)
        new_kwargs['osc_address'] = name
        universe = childgroup.add_child(**new_kwargs)
        #self.universes.update({universe.id:universe})
        #self.patch_by_chan.update({universe.id:{}})
        self.emit(sig, id=universe.id, universe=universe)
        self.emit('child_added')
        return universe
        
    def del_universe(self, **kwargs):
        univ = kwargs.get('universe')
        id = kwargs.get('id')
        if not univ:
            univ = self.universes.get(id)
            if not univ:
                univ = self.input_universes.get(id)
        childgroup = univ.ChildGroup_parent
        if len(self.patch_by_chan.get(univ.id)):
            return False
        univ.unlink()
        if childgroup == self.universes:
            del self.patch_by_chan[univ.id]
            del self.universes_by_id[univ.id]
        childgroup.del_child(univ)
        
    def _on_universes_child_added(self, **kwargs):
        univ = kwargs.get('obj')
        self.universes_by_id[univ.id] = univ
        self.patch_by_chan[univ.id] = {}
        
    def _on_universes_child_removed(self, **kwargs):
        univ = kwargs.get('obj')
        if univ.id in self.universes_by_id:
            del self.universes_by_id[univ.id]
        if univ.id in self.patch_by_chan:
            del self.patch_by_chan[univ.id]
        
    def add_category(self, **kwargs):
        cat_type = kwargs.get('type')
        name = kwargs.get('name')
        if cat_type in self.Categories:
            category = self.root_category.subcategories[cat_type].add_subcategory(**kwargs)
            #self.Categories[cat_type].update({category.id:category})
            self.emit('new_category', type=cat_type, name=category.name, id=category.id, category=category)
            self.emit('child_added')
            self.LOG.info('new_category:', cat_type, category.name)
        
    def add_global_attribute(self, **kwargs):
        '''
        :Parameters:
           'name' : default is empty
            'id' : Universal ID.  Default is None (which assigns a new uuid)
            'type' : class specification (in progress)
            'from_xml' : xml string which, if specified, the xml will be loaded
                into the attribute.  default is None. 
        '''
        attribute = self.GlobalAttributes.add_child(**kwargs)
        attribute.connect('id_changed', self.on_global_attrib_id_changed)
        #self.GlobalAttributes.update({attribute.id:attribute})
        self.emit('new_attribute', type='GlobalAttribute', id=attribute.id, name=attribute.name)
        self.emit('child_added')
        return attribute
        
    def del_global_attribute(self, **kwargs):
        attribute = kwargs.get('attribute')
        id = kwargs.get('id')
        if not attribute:
            attribute = self.GlobalAttributes.get(id)
        self.GlobalAttributes.del_child(attribute)
        #attribute.unlink()
        #del self.GlobalAttributes[attribute.id]
        self.emit('del_attribute', type='GlobalAttribute', id=attribute.id)
        
    def on_global_attrib_id_changed(self, **kwargs):
        pass
        
    def add_definition(self, **kwargs):
        '''
        :Parameters:
            'name' : default is empty
            'id' : Universal ID.  Default is None (which assigns a new uuid)
            'description' : default is empty
            'manufacturer' : default is empty
            'channel_count' : int, maximum number of channels for the device.
                default is 0
            'type' : device type name. default is 'NormalDevice'
            'from_xml' : xml string which, if specified, the xml will be loaded
                into the device.  default is None.
        '''
        device = self.DefinedDevices.add_child(**kwargs)
        self.emit('new_device', base=device.base_class, type=device.saved_class_name, id=device.id)
        self.emit('child_added')
        return device
        
    def del_definition(self, **kwargs):
        device = kwargs.get('device')
        id = kwargs.get('id')
        if not device:
            device = self.DefinedDevices.get(id)
        self.DefinedDevices.del_child(device)
        #device.unlink()
        #del self.DefinedDevices[device.id]
        self.emit('del_device', type='DefinedDevice', id=device.id)
        
    def patch_device(self, definition, **kwargs):
        '''Takes given device definition and make a patched
        device from it.
        
        :Parameters:
            'definition' : DefinedDevice to use
            'name' : Default is None (which uses definition name)
            'id' : Universal ID.  Default is None (which assigns a new uuid)
            'device_num' : int, index of the device to be used in the UI (for organization)
            'universe_id' : id of the DMX universe this device belongs to
            'start_chan' : int, starting DMX channel
            'count' : Number of devices to patch. Default is 1
        '''
        count = int(kwargs.get('count', 1))
        base_start = int(kwargs.get('start_chan', 1))
        start = base_start
        end = None
        new_kwargs = kwargs.copy()
        new_kwargs['definition'] = definition
        #new_kwargs.update({'osc_parent_node':self.osc_child_nodes['Devices']})
        #new_kwargs.update({'root_category':self.root_category.subcategories['device']})
        #new_kwargs.update({'definition':definition})
        for x in range(count):
            #print 'start=', start
            valid = self.test_patch(device=definition, universe_id=kwargs.get('universe_id'), 
                                    start_chan=start)
            if not valid:
                return end
            end = start + definition.channel_count
            #device_num = self._find_max_device_num() + 1
            new_kwargs['start_chan'] = start
            device = self.PatchedDevices.add_child(**new_kwargs)
            #device = Device.PatchedDevice(**new_kwargs)
            #device.connect('device_num_update', self.on_device_num_update)
            #self.PatchedDevices.update({device.id:device})
            #self.DevicesByNum.update({device.device_num:device})
            #self._update_attributes(device=device)
            self.emit('new_device', base='PatchedDevice', id=device.id, device=device)
            self.emit('child_added')
            start += definition.channel_count
        return end
        
    def repatch_device(self, **kwargs):
        '''Repatch an existing device
        :Parameters:
            'device' : 
            'universe_id' : new universe to patch into, or if not given,
                uses the current universe for the device
            'start_chan' : new starting DMX channel
        '''
        device = kwargs.get('device')
        univ_id = kwargs.get('universe_id', device.universe_id)
        start = kwargs.get('start_chan')
        old_univ = device.universe_id
        old_start = device.start_chan
        if not self.test_patch(**kwargs):
            return
        univ = self.universes_by_id[old_univ]
        for attrib in device.Attributes.itervalues():
            lsb = attrib.lsb_attribute
            if lsb:
                univ.del_attribute(lsb)
                #lsb.unbind(univ.on_attribute_value_changed)
                del self.patch_by_chan[old_univ][lsb.chan]
            univ.del_attribute(attrib)
            #attrib.unbind(univ.on_attribute_value_changed)
            del self.patch_by_chan[old_univ][attrib.chan]
        device.repatch(**kwargs)
        self._update_attributes(device=device)
        self.emit('patch_change', device=device, old_universe=old_univ, 
                  new_universe=univ_id, old_start=old_start, new_start=start)
            
    def unpatch_device(self, **kwargs):
        id = kwargs.get('id')
        device = kwargs.get('device')
        if not device:
            device = self.PatchedDevices.get(id)
        if not device or device.id not in self.PatchedDevices:
            return
        device.unpatch()
        for key, val in device.Attributes.iteritems():
            univ = self.universes_by_id[val.universe_id]
            lsb = val.lsb_attribute
            if lsb:
                univ.del_attribute(lsb)
                #lsb.unbind(univ)
                del self.patch_by_chan[val.universe_id][lsb.chan]
            univ.del_attribute(val)
            #val.unbind(univ)
            del self.patch_by_attrib[key]
            del self.patch_by_chan[val.universe_id][val.chan]
        self.PatchedDevices.del_child(device)
        self.emit('del_device', type='PatchedDevice', id=device.id)
        
    def _on_PatchedDevices_child_added(self, **kwargs):
        device = kwargs.get('obj')
        self._update_attributes(device=device)

    def find_first_start_chan(self, **kwargs):
        '''
        :Parameters:
            'start' : 
            'device' :
            'universe_id' :
            'count' : 
        '''
        start = kwargs.get('start', 1)
        count = kwargs.get('count', 1)
        device = kwargs.get('device')
        test_kwargs = kwargs.copy()
        flag = False
        for base_chan in range(start, 513):
            for i in range(count):
                test_kwargs['start_chan'] = base_chan + (device.channel_count * i)
                flag = self.test_patch(**test_kwargs)
                if not flag:
                    break
            if flag:
                return base_chan
        return False
    
    def test_patch(self, **kwargs):
        device = kwargs.get('device')
        univ_id = kwargs.get('universe_id')
        start = kwargs.get('start_chan')
        count = device.channel_count
        if start + count - 1 > 512:
            return False
        flag = True
        for x in range(start, count+start):
            attrib = self.patch_by_chan[univ_id].get(x)
            if attrib:
                if device.saved_class_name == 'DefinedDevice':
                    flag = False
                elif attrib != device.attr_by_chan.get(x):
                    flag = False
        return flag
        
#    def on_device_num_update(self, **kwargs):
#        device = kwargs.get('device')
#        value = kwargs.get('value')
#        old = kwargs.get('old')
#        if old is not None and old != value:
#            del self.DevicesByNum[old]
#            self.DevicesByNum[value] = device
#            print 'changed, old=%s, value=%s' % (old, value)
#        
#    def _find_max_device_num(self):
#        if len(self.DevicesByNum) == 0:
#            return 0
#        return max(self.DevicesByNum.keys())        
        
    def _update_attributes(self, **kwargs):
        kwargs.setdefault('device', None)
        kwargs.setdefault('device_id', None)
        kwargs.setdefault('attribute', None)
        device = kwargs.get('device')
        device_id = kwargs.get('device_id')
        attribute = kwargs.get('attribute')
        if attribute is not None:
            return
        if device_id is not None:
            device = self.PatchedDevices[device_id]
        if device is None:
            return
        for key, val in device.Attributes.iteritems():
            self.patch_by_attrib.update({val.id:val})
            if val.chan is None:
                continue
            self.patch_by_chan[val.universe_id].update({val.chan:val})
            lsb = val.lsb_attribute
            univ = self.universes_by_id[val.universe_id]
            univ.add_attribute(val)
            #univ.on_attribute_value_changed(obj=val, value=val.value)
            #val.bind(value=univ.on_attribute_value_changed)
            if lsb:
                self.patch_by_chan[val.universe_id].update({lsb.chan:lsb})
                univ.add_attribute(lsb)
                #univ.on_attribute_value_changed(obj=lsb, value=lsb.value)
                #lsb.bind(value=univ.on_attribute_value_changed)
                
    def on_new_ola_universe(self, **kwargs):
        univ = kwargs.get('ola_universe')
        for u in self.universes.itervalues():
            if u.ola_universe_id == univ.id:
                univ.Universe = u
        
    def clear_show(self, set_interrupts=False):
        if set_interrupts:
            self.osc_node.send_interrupt_enabled = True
        for key in self.PatchedDevices.keys()[:]:
            self.unpatch_device(id=key)
        for key in self.DefinedDevices.keys()[:]:
            self.del_definition(id=key)
        for key in self.GlobalAttributes.keys()[:]:
            self.del_global_attribute(id=key)
        for key in self.universes.keys()[:]:
            self.del_universe(id=key)
        for category in [self.root_category.subcategories[key] for key in ['device', 'attribute', 'effects']]:
            for subid in category.subcategories.keys()[:]:
                category.del_subcategory(id=subid)
        if set_interrupts:
            self.osc_node.send_interrupt_enabled = False
        
    def load_globals(self, **kwargs):
        kwargs['saved_child_objects'] = ['Categories', 'GlobalAttributes', 'DefinedDevices']
        self.from_json(**kwargs)
        
    def save_globals(self, **kwargs):
        return self._get_saved_attr(saved_child_objects=['Categories', 'GlobalAttributes', 'DefinedDevices'])
    
    def get_file_data(self, **kwargs):
        d = {}
        for key in ['Categories', 'GlobalAttributes', 'DefinedDevices', 'ShowData']:
            filename = '.'.join([key, 'js'])
            if key != 'ShowData':
                d[filename] = self._get_saved_attr(saved_child_objects=[key])
            else:
                objs = ['universes', 'input_universes', 'PatchedDevices']
                d[filename] = self._get_saved_attr(saved_child_objects=objs)
        return d
        
    def from_json(self, **kwargs):
        filename = kwargs.get('filename', None)
        string = kwargs.get('string', '')
        if string == '' and filename is not None and os.path.exists(filename):
            file = open(filename, 'r')
            for line in file:
                string += line
            file.close()
        if string != '':
            d = Serialization.from_json(string)
            self._load_saved_attr(d, **kwargs)
            
    def ChildGroup_prepare_child_instance(self, childgroup, cls, **kwargs):
        name = childgroup.name
        if name == 'universes':
            kwargs.update(dict(osc_parent_node=self.osc_universe_node, comm=self.comm))
        elif name == 'input_universes':
            kwargs.update(dict(out_universes=self.universes, comm=self.comm))
        elif name == 'GlobalAttributes':
            kwargs['root_category'] = self.root_category.subcategories['attribute']
        elif name == 'DefinedDevices':
            kwargs['DeviceSystem'] = self
        elif name == 'PatchedDevices':
            kwargs['DeviceSystem'] = self
        return cls, kwargs
            
    def _deserialize_child(self, d, **kwargs):
        saved_child_obj = kwargs.get('saved_child_obj')
        key = kwargs.get('key')
        if saved_child_obj == 'Categories':
            parent = self.root_category.find_category(id=key)
            if not parent:
                parent = self.root_category.add_subcategory(deserialize=d)
                self.Categories[key] = parent.subcategories
            for cKey, cVal in d.iteritems():
                category = self.root_category.find_category(id=cKey)
                if not category:
                    category = parent.add_subcategory(deserialize=cVal)
                    self.emit('new_category', type=key, name=category.name, id=category.id, category=category)
            return False
        return super(DeviceSystem, self)._deserialize_child(d, **kwargs)
        
    def _load_saved_attr(self, d, **kwargs):
        kwargs.setdefault('saved_child_objects', ['Categories', 'universes', 'input_universes', 'GlobalAttributes', 'DefinedDevices', 'PatchedDevices'])
        super(DeviceSystem, self)._load_saved_attr(d, **kwargs)
        return


class OSCDumpRequest(BaseThread):
    _Events = {'response_complete':{}}
    def __init__(self, **kwargs):
        kwargs['thread_id'] = 'DeviceSystem_dump_request'
        super(OSCDumpRequest, self).__init__(**kwargs)
        self.register_signal('dump_complete')
        self.DeviceSystem = kwargs.get('DeviceSystem')
        self._osc_dumps_pending = {}
        
    def run(self):
        self._running.set()
        ds = self.DeviceSystem
        self._osc_dumps_pending.clear()
        node = ds.osc_node.add_child(name='dump-response')
        node.bind(message_received=self.on_dump_response)
        self.dump_node = node
        objnames = ['universes', 'Categories', 'GlobalAttributes', 'DefinedDevices', 'PatchedDevices']
        ds.osc_node.send_message(address='request-dump', value=objnames, to_master=True)
        self.response_complete.wait()
        if self._running:
            self.emit('dump_complete')
            self.stop()
        
    def stop(self, **kwargs):
        blocking = kwargs.get('blocking')
        self.dump_node.unbind(self)
        self._running.clear()
        self.response_complete.set()
        self._stopped.set()
        if blocking:
            self.join()
        
    def on_dump_response(self, **kwargs):
        message = kwargs.get('message')
        values = message.get_arguments()
        if values[0] == 'dump-complete':
            self.DeviceSystem.load_osc_dump(self._osc_dumps_pending)
            self.response_complete.set()
            return
        self._osc_dumps_pending[values[0]] = values[1]
